
const delay = function(interval, call) {
    const timer = setTimeout(()=> {
        call();
        clearTimeout(timer);
    }, interval);
}

/**
 * 获取get参数
 * 
 * @param {String} name 参数名称
 * @param {String} defValue 默认值
 */
const get = function(name, defValue = '') {
    let src = location.search;
    src = src.substr(1, src.length - 1);
    const obj = param.toObject(src);
    if (_.has(obj, name)) {
        return obj[name];
    }
    return defValue;
}

/**
 * 对时间操作
 */
const time = {

    /**
     * 获取10位时间戳
     * 
     * @param {String} 需要转换的时间，不传时使用当前时间
     */
    get10(datetime) {
        const date = datetime ? new Date(datetime) : new Date();
        return Date.parse(date) / 1000;
    },

    /**
     * 获取13位时间戳
     * 
     * @param {String} 需要转换的时间，不传时使用当前时间
     */
    get13(datetime) {
        const date = datetime ? new Date(datetime) : new Date();
        return date.getTime();
    },

    /**
     * 取间隔的秒数或毫秒数 
     * 
     * @param {Int} startTimestamp 开始时间戳
     * @param {Int} endTimestamp 结束时间戳
     */
    interval(startTimestamp, endTimestamp) {
        return endTimestamp - startTimestamp;
    },

    /**
     * 倒计时
     * 
     * @param {Int} timestamp 时间戳（必须是10位时间戳）
     */
    countdown(timestamp) {

        // 取时间秒数差
        let interval = timestamp - this.get10();

        // 计算时间差中的日时分秒（时分秒补0）
        let day = Math.floor(interval / (60 * 60 * 24)).toString();
        let hour = this._zero(Math.floor(interval / (60 * 60)) - (day * 24)).toString();
        let minute = this._zero(Math.floor(interval / 60) - (day * 24 * 60) - (hour * 60)).toString();
        let second = this._zero(Math.floor(interval) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60)).toString();

        // 返回一个对象
        return { day, hour, minute, second };
    },

    /**
     * 将当前时间或指定时间戳转换为指定时间格式的字符串
     * 
     * @param {String} formats 需要转换为的时间格式，默认为：Y-m-d
     * @param {Int} timestamp 时间戳，默认为当前时间
     */
    format(formats, timestamp) {

        // 参数默认值 
        formats = formats || 'Y-m-d';
        let date = timestamp ? new Date(timestamp) : new Date();

        // 取年月日 
        let year = date.getFullYear();
        let month = this._zero(date.getMonth() + 1);
        let day = this._zero(date.getDay());
        let hour = this._zero(date.getHours());
        let minute = this._zero(date.getMinutes());
        let second = this._zero(date.getSeconds());

        // 替换返回
        return formats.replace(/Y|m|d|H|i|s/ig, function (matches) {
            return ({
                Y: year,
                m: month,
                d: day,
                H: hour,
                i: minute,
                s: second
            })[matches];
        });
    },

    /**
     * 补时间位上的先导0
     * 
     * @param {Int} value 值
     */
    _zero(value) {
        if (value < 10) {
            return '0' + value;
        }
        return value;
    }
}

/**
 * 为页面参数操作提供基础支持：
 * 当前提供的功能：
 *      toObject(src): 将参数从querystring类型转换为object类型；
 *      toString(src)：将参数从object类型转换为querystring类型；
 *      join(path, params): 连接路径与querystring参数
 */
const param = {

    /**
     * 将QueryString形式的参数转换为Object形式的参数
     * 
     * @param {String} src 源字符串
     */
    toObject(src) {
        
        // 检查参数
        if (!_.isString(src)) console.error('Param.toObj的参数src，必须填写且必须为String类型！');

        // 分解为数组，如果数组长度等于0，则直接返回空对象
        const arr = src.split('&');
        if (arr.length == 0) return {};

        // 声明需要返回的对象
        let result = {};

        // 遍历转换为对象
        for (let item of arr) {
            
            // 将item分解为左右2边的数组，如果数组长度小于2，则continue
            const tmp = item.split('=');
            if (tmp.length < 2) continue;

            // 将数组内容添加到对象
            result[tmp[0]] = tmp[1];
        }

        // 返回
        return result;
    },

    /**
     * 将Object形式的参数转换为QueryString形式的参数
     * 
     * @param {Object} src 源对象
     */
    toString(src) {

        // 检查参数
        if (!_.isObject(src)) console.error('Param.toStr的参数src，必须填写且必须为Object类型！');

        // 声明存放临时数据的数组
        let array = [];

        // 遍历src
        for (let key in src) {
            array.push(key + '=' + src[key]);
        }

        // 返回连接&符之后的数组
        return array.join('&');
    },

    /**
     * 连接路径与参数（必须是querystring）
     * 
     * @param {String} path 路径字符串
     * @param {String} params 参数字符串（必须是querystring）
     */
    join(path, params) {

        // 检查参数
        if (!_.isString(path)) console.error('Param.join的参数path，必须填写且必须为String类型！');
        if (!_.isString(params)) console.error('Param.join的参数params，必须填写且必须为String类型！');

        // 如果params是空字符串，则直接返回path
        if (params == '') return path;

        // 初始化符号为？，如果已存在?，则转换为&
        let symbo = '?';
        if (path.indexOf('?') >= 0) {
            symbo = '&';
        }
        return path + symbo + params;
    }
}


/**
 * 
 * 
 * @param {Object} obj 
 */
const http = {
    
    /**
     * 和社区get请求
     * 
     * @param {Object} obj 设置对象 
     */
    get(obj) { 
        obj = this._setConfig(obj);
        return this._request('GET', obj);
    },

    /**
     * 和社区post请求
     * 
     * @param {Object} obj 设置对象 
     */
    post(obj) {
        obj = this._setConfig(obj);
        return this._request('POST', obj);
    },

    /**
     * 原始get请求
     * 
     * @param {Object} obj 设置对象 
     */
    getOrigin(obj) {
        obj = this._setConfig(obj, true);       // 第二个参数为false，代表原始访问（不加url前缀、不设auths、不解析返回值）
        return this._request('GET', obj);
    },

    /**
     * 原始post请求
     * 
     * @param {Object} obj 设置对象 
     */
    postOrigin(obj) {
        obj = this._setConfig(obj, true);       // 第二个参数为false，代表原始访问（不加url前缀、不设auths、不解析返回值）
        return this._request('POST', obj);
    },

    /**
     * 真实访问方法
     * 
     * @param {Object} obj 设置对象 
     */
    _request(method, obj) {

        // 设置obj请求方法
        obj['method'] = method;

        // 执行开始回调
        this._start(obj);

        // ------------------------------------------------------ 
        // 设置url（主要用于get）
        if (obj.method == 'GET' && _.isObject(obj.data)) {
            obj.url = obj.url + this._paramGet(obj.url, obj.data);
        }
        
        // ------------------------------------------------------ 
        // 设置body（主要用于post）
        // 注：如果不需要添加body字段，则Param.post函数返回的是null
        if (obj.method == 'POST') {
            let result = this._paramPost(obj.data);
            if (result != null) {
                obj['body'] = result;
            }
        }
        
        // ------------------------------------------------------ 
        // 发起请求，并返回数据
        // 判断是否存在服务器状态码错误，最后将文本类型的返回值交给下一步
        fetch(obj.url, obj).then((res)=> {

            // 在debug模式下，输出response信息
            console.log('--------- ' + obj.method + ' : ' + obj.url);
            if (_.isObject(obj.data)) {
                console.log(obj.data);
            }
            console.log(res);
            console.log('--------- END');

            // 取状态码
            const statusCode = parseInt(res.status);

            // 状态码出错，调用管道函数，返回状态出错，并返回给下一步null（下一个then使用）
            if (statusCode != 200) {
                if (_.isFunction(obj.pipe.fail)) {
                    obj.pipe.fail(res, obj);
                }
                return null;
            }

            // 将数据返回给下一步，注意，这里全部返回为text类型
            return res.text(); 
        })

        // ------------------------------------------------------ 
        // 数据处理
        .then(async (text)=> {

            // 执行结束回调
            this._end(obj);

            // 空值判断，返回码不为200时，text为null，则直接返回
            if (text == null) return;
            
            // obj.isOrigin为true，直接返回text
            if (obj.isOrigin) {
                if (_.isFunction(obj.pipe.success)) {
                    obj.pipe.success(text, obj);
                }
            }

            // hesq请求，返回解析后的结果
            else {
                this._parse(text, obj);
            }
        })

        // ------------------------------------------------------ 
        // 错误抓取
        .catch((err) => {
            
            // 执行结束回调
            this._end(obj);

            // 一但抓取到错误，立即报出，用于调试（正式环境会收集数据）
            console.error(err);

            // 调用失败管道函数
            if (_.isFunction(obj.pipe.fail)) {
                obj.pipe.fail(err, obj);
            }
        });
    },

    /**
     * 对返回文本的解析（仅和社区访问服务端可用）
     * 
     * @param {String} text 从服务器返回的原始字符串
     * @param {Object} obj 设置对象
     */
    _parse(text, obj) {
        // 判断字符串是否能被解析为json对象，如果成功则将text转换为json，否则报fail
        // taro有时会自动将字符串转换为json，所以，这里需要做判断，看text是否为string，如果已经是json，就不要转转了
        if (_.isString(text)) {
            try {
                text = JSON.parse(text);
            } 
            
            // 转换出错，调用管道函数，记录错误、返回fail
            catch(e) {
                if (_.isFunction(obj.pipe.fail)) {
                    obj.pipe.fail(text, obj);
                }
                console.error(e);
                return;
            }
        }

        // 转换为json
        const json = _.isString(text) ? JSON.parse(text) : text;

        // json格式出错，调用管道函数，返回fail
        if (!_.has(json, 'code') || !_.has(json, 'msg')) {
            if (_.isFunction(obj.pipe.fail)) {
                obj.pipe.fail(json, obj);
            }
        }

        // 逻辑判断，如果code不等于0，调用管道函数，返回error
        else if (parseInt(json.code) != 0) {
            if (_.isFunction(obj.pipe.error)) {
                obj.pipe.error(json, obj);
            }
        }

        // 已经全部正确正确，调用success回调
        else {
            if (_.isFunction(obj.pipe.success)) {
                obj.pipe.success(json.data, obj);
            }
        }
    },

    /**
     * 检查obj参数，并使用obj根下的pipe函数，覆盖掉obj.pipe下的pipe函数
     * 
     * @param {Object} obj http请求设置对象
     */
    _setConfig(obj, isOrigin = false) {

        // 检查http请求对象中的：pipe是否全部存在
        if (!_.isObject(obj.pipe)) {
            console.error('http请求对象参数obj，至少pipe参数。且pipe必须为Object类型！', obj);
            return false;
        }

        // 设置isOrigin、url、auths
        obj['isOrigin'] = isOrigin;
        obj['headers'] = {
            'content-type': 'application/x-www-form-urlencoded',
            // 'Authorization': isOrigin ? '' : '' // JSON.stringify(Power.getHttpAuths());
        };

        // obj根下管道函数，覆盖obj.pipe管道函数
        const customePipe = _.omit(obj, ['url', 'data', 'pipe']);
        obj['pipe'] = Object.assign(obj.pipe, customePipe);

        // 返回
        return obj;
    },

    /**
     * 设置Get方法的参数
     * 
     * @param {String} url 此次访问的url
     * @param {Object} data 数据对象
     * @return {String} 需要连接到url之后的querystring
     */
    _paramGet(url, data) {

        // 如果不是平面对象，则直接返回空串
        if (!_.isPlainObject(data)) {
            return '';
        }

        // 计算params数组
        let params = [];
        for (let key in data) {
            params.push(key + '=' + data[key]);
        }

        // 计算得到连接符号和查询字符串
        const symbol = url.indexOf('?') === -1 ? '?' : '&';
        const query = params.join('&');

        // 返回连接在url之后需要连接的参数字符串
        return symbol + query;
    },

    /**
     * 设置Post方法的参数
     * 
     * @param {Object} data 数据对象
     * @return {FormData} 如果存在data，则返回新的formData数据，否则返回null
     */
    _paramPost(data) {

        // 如果不是平面对象，则直接返回false（代表不需要在obj添加body）
        if (!_.isPlainObject(data)) {
            return null;
        }

        // 返回
        return param.toString(data);
    },

    /**
     * 如果带有指示器，则显示指示器
     * 注意start和end都必须是方法
     * 
     * @param {Object} obj 设置对象
     */
    _start(obj) {
        if (_.isFunction(obj.pipe.start)) {
            obj.pipe.start();
        }
    },

    /**
     * 如果带有指示器，则隐藏指示器
     * 注意start和end都必须是方法
     * 
     * @param {Object} obj 设置对象
     */
    _end(obj) {
        if (_.isFunction(obj.pipe.end)) {
            obj.pipe.end();
        }
    }
}
